QMI 拨号&网口
记录 9x07平台的网口相关功能的研究过程,包含QMI、smd、网口等功能
rmnet USB网口
初始化
创建5控制接口misc设备,供应用层使用
function初始化rmnet_function_init —> rmnet_init —> gqti_ctrl_init
rmnet_ctrl、rmnet_ctrl1、rmnet_ctrl2、rmnet_ctr3、dpl_ctrl
初始化USB端点
应用层:echo QTI,BAM_DMUX > f_rmnet/transports
frmnet_init_port,初始化网络接口设备和端口:f_rmnet、rmnet_port
- 控制接口类型:QTI
- 数据接口类型:BAM_DMUX
frmnet_bind,初始化function interface
创建3个endpoint
数据TX,从机 —> 主机(IN endpoint)
数据RX,主机 —> 从机(OUT endpoint)
事件通知,从机 —> 主机(IN endpoint)
初始接口和端点的描述
通讯方式
应用层打开/dev/rmnet_ctrl和/dev/dpl_ctrl
应用层通过/dev/rmnet_ctrl和/dev/dpl_ctrl与host端程序通讯
设置接口
usb连接时,host端驱动执行USB接口设置,触发frmnet_set_alt
- 初始化&打开事件通知端点
- 执行gport_rmnet_connect,连接初始化
- 控制通道连接初始化
- 数据通道连接初始化
控制通道连接初始化
gsmd_ctrl_connect
初始化2个回调函数
1 | g_rmnet->send_encap_cmd = gqti_ctrl_send_cpkt_tomodem; |
执行g_rmnet->connect(port->port_usb) —> frmnet_connect
数据通道连接初始化
gbam_connect
初始化&打开数据TX、数据RX端点
触发gbam_connect_work
1 | INIT_WORK(&port->connect_w, gbam_connect_work); |
gbam_connect_work
打开bam
1 | ret = msm_bam_dmux_open(d->id, port, gbam_notify); |
为TX和RX端口申请usb_request
gbam_start_io —> _gbam_start_io —> gbam_alloc_requests
TX和RX端点的申请大小及完成回调
1 | if (in) { |
通讯
host向device发命令
host驱动 —> usb —> device应用
使用usb的setup,由frmnet_setup处理理
命令:USB_CDC_SEND_ENCAPSULATED_COMMAND
1 | case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) |
frmnet_cmd_complete —> dev->port.send_encap_cmd —> gqti_ctrl_send_cpkt_tomodem
其中参数port使用ctrl_xport_num
1 | case USB_GADGET_XPORT_QTI: |
gqti_ctrl_send_cpkt_tomodem
将数据放到队列中,唤醒读取的任务
1 | port = ctrl_port[portno]; //端口号为0,使用/dev/rmnet_ctrl |
device应用读取
qti_ctrl_read
1 | cpkt = list_first_entry(&port->cpkt_req_q, struct rmnet_ctrl_pkt, |
device向host发命令
device应用 —> usb —> host驱动
qti_ctrl_write
1 | ret = copy_from_user(kbuf, buf, count); |
1 | dev->port.send_cpkt_response = frmnet_send_cpkt_response; |
frmnet_send_cpkt_response
1 | cpkt = rmnet_alloc_ctrl_pkt(len, GFP_ATOMIC); |
host收到消息后再通过setup来取数据
1 | case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) |
host向device发数据
host驱动 —> usb —> bam —> modem
host通过数据RX端点(OUT)发数据
device收到数据,执行gbam_epout_complete
1 | skb_put(skb, req->actual); |
数据存放到链表,通知处理任务
1 | INIT_WORK(&d->write_tobam_w, gbam_data_write_tobam); |
gbam_data_write_tobam —> msm_bam_dmux_write
device向host发数据
modem —> bam —> usb —> host驱动
- 数据通道连接初始化时open bam时设置了回调gbam_notify
- gbam_notify处理消息BAM_DMUX_RECEIVE
1 | switch (event) { |
gbam_data_recv_cb —> gbam_write_data_tohost
BAM数据收发
bam支持的BAM通道非常多,USB用了其中2个
1 | static unsigned bam_ch_ids[BAM_N_PORTS] = { |
初始化时申请了2个bam端口供usb使用
1 | static int gbam_port_alloc(int portno) |
发送数据
gbam_data_write_tobam —> msm_bam_dmux_write
1 | int msm_bam_dmux_write(uint32_t id, struct sk_buff *skb) |
接收数据
bamr接收数据入口:__queue_rx —> handle_bam_mux_cmd —> bam_mux_process_data
接收的数据包中通道ID,根据通道找到notify,msm_bam_dmux_open打开通道时传入
modem 网口
网口创建
高通原始代码中网口名字不叫modem,面是rmnet_data
kernel初始化时默认没有创建rmnet网口
- rmnet创建入口:bam_rmnet_probe
kernel初始化时创建了bam_dmux_ch_xx(xx表示有很多)平台驱动
启动后收到了bam数据(命令:BAM_MUX_HDR_CMD_OPEN),添加平台设备bam_dmux_ch_0
handle_bam_mux_cmd —> handle_bam_mux_cmd_open —> platform_device_add
触发bam_rmnet_probe
创建rmnet0网口,用于控制交互
- rmnet_data创建入口: rmnet_vnd_create_dev
- rmnet_config_netlink_msg_handler –> rmnet_create_vnd –> rmnet_vnd_create_dev
- 创建8个网口,rmnet_data0 - rmnet_data7,用于数据收发
netmgrd初始化,将rmnet和和rmnet_data绑定
使用 RMNET_NETLINK_SET_LOGICAL_EP_CONFIG命令
_rmnet_netlink_set_logical_ep_config –> rmnet_set_logical_endpoint_config
将rmnet_data0的epconfig.egress_dev指向rmnet0
打开网口
数据收发需要使用bam,因此需要打开bam
netmgrd进程使用ioctl打开了rmnet0网口
1
2
3
4
5
6
7
8
9
10netmgr_kif_ifioctl_open_port (const char * dev)
{
/* Open a datagram socket to use for issuing the ioctl */
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
//此处使用的接口名为rmnet0
/* Set device name in ioctl req struct */
(void)strlcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
/* Get current if flags for the device */
if (ioctl(fd, RMNET_IOCTL_OPEN, &ifr) < 0) {
}kernel中执行rmnet0网口的open操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
case RMNET_IOCTL_OPEN: /* Open transport port */
rc = __rmnet_open(dev);
static int __rmnet_open(struct net_device *dev)
{
int r;
struct rmnet_private *p = netdev_priv(dev);
if (p->device_up == DEVICE_UNINITIALIZED) {
r = msm_bam_dmux_open(p->ch_id, dev, bam_notify);
}
p->device_up = DEVICE_ACTIVE;bam只打开一次,关闭接口时也不关闭bam
发送数据
数据收发使用rmnet_data0接口(现已改名成modem)
rmnet_data0接口的发送函数:rmnet_vnd_start_xmit
1 | static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb, |
数据由rmnet_egress_handler处理,其中dev_conf->local_ep.egress_dev在前面有初始化
1 | void rmnet_egress_handler(struct sk_buff *skb, |
触发rmnet0的发送队列:rmnet_xmit —> _rmnet_xmit —> msm_bam_dmux_write
接收数据
打开bam时传入了notify函数bam_notify
1 | static void bam_notify(void *dev, int event, unsigned long data) |
rmnet处理数据包:__rmnet_deliver_skb
数据包从rmnet0进入协议栈,rmnet_data0会向协议栈注册一些回调用于数据包处理,比如更新rmnet_data0的统计值:rmnet_vnd_rx_fixup,
qti数据处理
应用层启动了一个qti进程,负责与host端的驱动交互,同时通过/dev/smdcntl8与modem交互
qti接收host端数据
host端发的数据通过/dev/rmnet_ctrl到达qti进程
数据由qti_rmnet_ph_recv_msg处理,再调用qti_rmnet_modem_send_msg处理
- 数据交给qti_rmnet_process_qmi_tx_to_modem处理一遍(可能会修改数据)
- 数据转发给/dev/smdcntl8(发给modem)
注:首次接收到数据,需要打开&初始化与modem侧的通信通道,否则smdcntl8不能通信
qti接收modem数据
modem发的数据通过/dev/smdcntl8到达qti进程
数据由qti_rmnet_modem_recv_msg处理
数据交给qti_rmnet_process_qmi_rx_from_modem处理一遍
qti修改某些服务的数据后再转给host端
数据转发给/dev/rmnet_ctrl(发给host)
打开通信通道
qti_rmnet_ph_recv_msg —> qti_rmnet_process_ph_reset
首先确认USB是否确实已经连接
1
2ret = ioctl(rmnet_state_config->ph_iface[PH_DRIVER_TYPE_USB].ph_iface_fd,
FRMNET_CTRL_GET_LINE_STATE, &line_state);已连接且通道没有打开,则执行打开&初始化
1
2
3
4
5
6
7
8
9
10
11if(line_state == 1)
{
if(!rmnet_state_config->dtr_enabled)
{
//向dpm服务发请求,打开DATA40_CNTL通道
ret_val = qti_rmnet_dpm_port_open();
//打开smdcntl8接口,绑定qti_rmnet_modem_recv_msg处理modem的数据
if(qti_rmnet_modem_init(rmnet_state_config,
dpl_state_config) < 0)
rmnet_state_config->dtr_enabled = 1;打开DATA40_CNTL通道后会触发Kernel里创建SDM设备(SDM里有描述)
dpm服务的初始化
进程启动时调用了qti_dpm_init,初始化了DMP qmi客户端
QMI的初始化在QMI客户端
qmuxd进程
qmuxd进程提供qmi服务
qmuxd创建/tmp/qmux_connect_socket供客户端使用
linux_qmi_qmux_if_get_listener_socketlinux_qmi_qmux_if_configure_ports,默认关闭了所以端口
1
2
3
4
5
6/* Disabling all channels initially to enable only required channels later.*/
for (i = QMI_CONN_ID_RMNET_0; i < LINUX_QMI_MAX_CONN_SUPPORTED; i++)
{
qmi_qmux_disable_port(linux_qmi_conn_id_enablement_array[i].qmi_conn_id,
linux_qmi_conn_id_enablement_array[i].data_ctl_port, TRUE);
}客户通过qmux_connect_socket连接qmuxd
- 为客户端分配一个id:qmux_client_id
- 将qmux_client_id发给客户端,后续客户端需要用这个id来通讯
- qmuxd为客户端创建数据结构,更新linux_qmi_qmux_if_client_id_array
客户端发数据
- client进程 —> qmux_connect_socket —> qmuxd进程
- qmuxd进程使用qmux_client_id(连接时分配的)发数据,处理函数:qmi_qmux_tx_msg
- client发数据的消息头里包含qmi_conn_id,既数据发给谁,默认rmnet0
- qmi_qmux_if_internal_use_conn_id
- 客户商使用qmi_qmux_if_send_to_qmux函数发数据
- 最终调用 使用linux_qmi_qmux_if_client_tx_msg
- 数据面使用qmi_qmux_if_send_qmi_msg(msg_id固定为QMI_QMUX_IF_QMI_MSG_ID)
- 控制面使用qmi_qmux_if_send_if_msg_to_qmux(可指定msg_id)
qmuxd接收客户端的消息
- main中从客户端接收数据:linux_qmi_qmux_if_server_process_client_msg
- 使用qmi_qmux_tx_msg转发到消息
qmuxd处理客户端数据:qmi_qmux_tx_msg
第一次需要连接:qmi_qmux_open_connection —> linux_qmi_qmux_io_open_conn
因为默认所有的端口都被关闭,因此默认连接不成功
默认客户端使用rmnet0
按照客户使用的msg_id做不同分类处理
- 客户数据面使用QMI_QMUX_IF_QMI_MSG_ID,数据转发到modem
- 由qmi_qmux_tx_to_modem处理
- 默认使用rmnet0,对应的控制节点为/dev/smdctl0
- 因为端口默认是关闭的,正常数据不会被发送
- 客户数据面使用QMI_QMUX_IF_QMI_MSG_ID,数据转发到modem
qmi客户端
初始化
libqmiserver.so中有函数void __attribute__ ((constructor)) qmi_fw_cci_init(void),在main函数之前就运行,添加了一些port到xport_tbl,比如:
1 | qmi_cci_xport_start(&qcci_ipc_router_ops, NULL);//最先添加,优先使用 |
qmi_client_init_instance —> qmi_client_get_service_instance —> qmi_client_get_service_list查的服务
- 遍历xport_tbl表,使用lookup函数(xport_lookup)查找服务
- qmi_client_init初始化客户端
xport_lookup
优先使用qcci_ipc_router_ops的xport_lookup
- 通过AF_MSM_IPC连接到内核
- 使用ioctl(命令:IPC_ROUTER_IOCTL_LOOKUP_SERVER)从内核查找服务
- 查找使用service(服务ID)和instance(服务版本)
- 内核提供的服务查看文件:/sys/kernel/debug/msm_ipc_route/dump_servers
其次使用qmuxd_ops的xport_lookup
查找之前需要边连接qmuxd
如果连接不上(qmuxd进程没有启动)则等待1分钟,尝试60次
qmuxd里关闭了接口,实际不生效,获取不到服务
注:虽然实际没使用qmuxd,但qmuxd进程不启动时会导致客户端每次请求等待1分钟
qmi_client_init
找出服务提供者,默认服务由qcci_ipc_router_ops提供
初始化客户端内部使用的clnt,细节在qmi_cci_client_alloc里
打开服务,ops->open —> xport_open
- 保存open的返回句柄,给后续使用
xport_open
获取服务时得到一个地址addr,后续数据通讯使用这个地址
服务由qcci_ipc_router_ops,所有的操作路由到kernel,由kernel处理
初始化控制消息read线程:ctrl_msg_reader_thread
进程只有一个控消息线程
打开socket,得到到fd,给线程用
- 发命令IPC_ROUTER_IOCTL_BIND_CONTROL_PORT到kernel
- 创建线程
初始化数据消息read线程:data_msg_reader_thread
- 进程可以有多个数据息线程
- 打开socket,得到到fd,给线程用
- 创建线程
发送数据
qmi_client_send_msg_sync
- 根据handle找出clnt(在qmi_cci_client_alloc中创建)
- 从clnt中找到xport(在qmi_client_init中添加,在qmi_client_get_service_instance中初始化)
- 数据编码&发送:encode_and_send
- 使用qmi_cci_send发送数据
- 使用服务提供的send发送数据:xport_send
- 数据发给kernel(携带了服务地址)
- 等待数据回应:qmi_cci_response_wait_loop
- 数据消息read线程会通知当前线程
读取数据
data_msg_reader_thread
- 从kernel读取数据,转发到应用程序
- 从kernel读取控制消息,主要用于服务通知
- 如果服务启动晚于应用初始化,qmi_client_init_instance 会等待服务
ipc router
从名字上看是进程间通讯路由功能,实际是实现了一个sock通讯
server <----> ipc sock core <----> client---->---->
初始化
sock_register(&msm_ipc_family_ops)
注册IPC sock类型,名字为MSM_IPC
添加诊断服务
诊断服务不是重点,不关注细节
kernel启动时注册了许多诊断服务:diag_socket_init —> __diag_socket_init
服务地址:
1 | info->svc_id = DIAG_SVC_ID; |
诊断服务的创建:socket_open_server
1 | info->hdl->sk->sk_data_ready = socket_data_ready; |
QMI服务注册
modem 发送请求 —-> linux创建服务
modem给linux发命令:IPC_ROUTER_CTRL_CMD_NEW_SERVER,linux侧添加服务
1 | case IPC_ROUTER_CTRL_CMD_NEW_SERVER: |
注:新建服务时使用xprt_info,后续客户端收发数据使用xprt_info->xprt提供的接口
process_new_server_msg
使用服务的ID(node_id, port_id)创建entry和port供后续查找数据结构使用
1 | rt_entry = ipc_router_get_rtentry_ref(msg->srv.node_id); |
使用服务地址(service, instance)创建服务
1 | server = msm_ipc_router_create_server( |
xprt_info和modem数据如何达到linux侧的细节在后面讲
ipc route smd
实现linux侧数据与共享内存设备(包含modem)数据交互,以modem举例
server(modem) <----> ipc sock core <----> client(应用app)---->---->
初始化
msm_ipc_router_smd_xprt_init,添加平台驱动:ipc_router_smd_xprt
设备树配置
qcom,ipc_router_modem_xprt {
compatible = "qcom,ipc_router_smd_xprt";
qcom,ch-name = "IPCRTR";
qcom,xprt-remote = "modem";
qcom,xprt-linkid = <1>;
qcom,xprt-version = <1>;
qcom,fragmented-data;
qcom,disable-pil-loading;
};
probe时初始化服务: msm_ipc_router_smd_xprt_probe —> msm_ipc_router_smd_config_init
1 | static int msm_ipc_router_smd_config_init( |
使用设备树里的名字IPCRTR注册平台驱动
1 | static int msm_ipc_router_smd_driver_register( |
如果创建IPCRTR平台设备,触发msm_ipc_router_smd_remote_probe
1 | static int msm_ipc_router_smd_remote_probe(struct platform_device *pdev) |
注意:此处需要一个IPCRTR平台设备才能触发probe,IPCRTR设备在哪里创建?
IPCRTR平台设备的创建在SDM里
QMI数据收发
发送数据
应用层发数据到达qcci_ipc_router_ops->xport_send,细节在qmi客户端有讲
xport_send的数据到到kernel的msm_ipc_router_sendmsg
- xport_send发数据时携带了服务地址,数据由kernel路由到指定的服务
- msm_ipc_router_sendmsg发数据时携带源端口(包含client进程sock信息)
继续调用发送:msm_ipc_router_send_to
1 | ... |
填充包头&发送:msm_ipc_router_write_pkt
1 | hdr = &(pkt->hdr); |
其中 xprt_info->xprt在msm_ipc_router_smd_config_init中创建,对应的write函数为
msm_ipc_router_smd_remote_write —> smd_write_segment
1 | int smd_write_segment(smd_channel_t *ch, const void *data, int len) |
接收数据
就用层打开IPC_MSM类型的sock,从kernel的msm_ipc_router_recvmsg接收数据
1 | static int msm_ipc_router_recvmsg(struct kiocb *iocb, struct socket *sock, |
读取数据,从列队中取出数据
1 | int msm_ipc_router_read(struct msm_ipc_port *port_ptr, |
重点是数据怎么放入队列中
1 | static void do_read_data(struct work_struct *work) |
其中do_read_data的调用在Modem发数据到Linux有详细说明
加密认证
客户端发送数据之前需要配置加密认证方式,否则不能发数据
1 | static int msm_ipc_router_sendmsg(struct kiocb *iocb, struct socket *sock, |
认证配置由应用层irsc_util进程负责
irsc_util
ipc router转发数据之前需要配置加密,irsc_util进程负责设置配置
系统启动时执行:/usr/bin/irsc_util /etc/sec_config
默认情况不存在/etc/sec_config文件,则使用默认配置
irsc_util代码:
1 | fd = socket(AF_MSM_IPC, SOCK_DGRAM, 0); |
进入到内核代码:
1 | case IPC_ROUTER_IOCTL_CONFIG_SEC_RULES: |
Modem发数据到Linux
modem发数据给linux总体思路
- 数据存入共享内存
- 通知AP侧(linux侧)的CPU
- AP侧CPU收到中断
- 中断中处理数据:handle_smd_irq
具体数据处理函数在smd_alloc_channel注册
modem侧打开端口
modem发数据之前发送打开端口命令,触发msm_ipc_router_smd_remote_notify
事件是SMD_EVENT_OPEN
1 | case SMD_EVENT_OPEN: |
继续执行打开
1 | static void smd_xprt_open_event(struct work_struct *work) |
继续打开
1 | case IPC_ROUTER_XPRT_EVENT_OPEN: |
添加一个端口
1 | static void xprt_open_worker(struct work_struct *work) |
创建读取任务
1 | INIT_WORK(&xprt_info->read_data, do_read_data); |
接收modem数据
modem有数据发给linux时,触发msm_ipc_router_smd_remote_notify
事件是SMD_EVENT_DATA
1 | case SMD_EVENT_DATA: |
其中md_xprtp->read_work在msm_ipc_router_smd_config_init里初始化
1 | INIT_DELAYED_WORK(&smd_xprtp->read_work, smd_xprt_read_data); |
smd_xprt_read_data
从modem侧读取数据包,放入队列
1
2
3
4skb_queue_tail(smd_xprtp->in_pkt->pkt_fragment_q, ipc_rtr_pkt);
msm_ipc_router_xprt_notify(&smd_xprtp->xprt,
IPC_ROUTER_XPRT_EVENT_DATA,
(void *)smd_xprtp->in_pkt);继续放入队列
1
2
3
4
5
6
7
8
9
10
11
12
13
14void msm_ipc_router_xprt_notify(struct msm_ipc_router_xprt *xprt,
unsigned event,
void *data)
{
pkt = clone_pkt((struct rr_packet *)data);
if (!pkt)
return;
mutex_lock(&xprt_info->rx_lock_lhb2);
list_add_tail(&pkt->list, &xprt_info->pkt_list);
__pm_stay_awake(&xprt_info->ws);
mutex_unlock(&xprt_info->rx_lock_lhb2);
queue_work(xprt_info->workqueue, &xprt_info->read_data);
}
唤醒读取任务,执行do_read_data
处理modem的数据
do_read_data
modem发过来的数据包格式
1 | struct rr_header_v1 { |
按照不同类型创建执行不同操作,其中控制消息
1 | if (hdr->type != IPC_ROUTER_CTRL_CMD_DATA) { |
其中包含创建服务
1 | switch (msg->cmd) { |
SMD
共享内存设备
partitions
smem_areas
初始化
下表为支持了smd(共享内存设备)
1 | enum { |
linux启动时会为其初始化,创建工作队列probe_work
1 | for (i = 0; i < NUM_SMD_SUBSYSTEMS; ++i) { |
一个远端设备有多个通道,默认支持64个通道
创建SDM设备
modem启动后会向linux发消息,触发smd中断,linux侧执行smd_modem_irq_handler
smd_modem_irq_handler —> handle_smd_irq —> do_smd_probe
1 | static void do_smd_probe(unsigned remote_pid) |
远端设备(比如modem)通过共享内存区域传递数据到linux侧,如果有free_space有变化(细节暂不研究)则触发smd_channel_probe_worker
smd_channel_probe_worker —> smd_channel_probe_now —> smd_alloc_channel
从共享内存获取设备信息,得到设备名字(比如IPCRTR)
遍历所有通道 ,对未申请的通道进行申请
申请通道时会注册平台设备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57static int smd_alloc_channel(struct smd_alloc_elm *alloc_elm, int table_id,
struct remote_proc_info *r_info)
{
/* probe_worker guarentees ch->type will be a valid type */
if (ch->type == SMD_APPS_MODEM) //设备类型是SMD_APPS_MODEM
ch->notify_other_cpu = notify_modem_smd;
else if (ch->type == SMD_APPS_QDSP)
ch->notify_other_cpu = notify_dsp_smd;
else if (ch->type == SMD_APPS_DSPS)
ch->notify_other_cpu = notify_dsps_smd;
else if (ch->type == SMD_APPS_WCNSS)
ch->notify_other_cpu = notify_wcnss_smd;
else if (ch->type == SMD_APPS_Q6FW)
ch->notify_other_cpu = notify_modemfw_smd;
else if (ch->type == SMD_APPS_RPM)
ch->notify_other_cpu = notify_rpm_smd;
if (smd_is_packet(alloc_elm)) {
//是包类型
ch->read = smd_packet_read;
ch->write = smd_packet_write;
ch->read_avail = smd_packet_read_avail;
ch->write_avail = smd_packet_write_avail;
ch->update_state = update_packet_state;
ch->read_from_cb = smd_packet_read_from_cb;
ch->is_pkt_ch = 1;
} else {
ch->read = smd_stream_read;
ch->write = smd_stream_write;
ch->read_avail = smd_stream_read_avail;
ch->write_avail = smd_stream_write_avail;
ch->update_state = update_stream_state;
ch->read_from_cb = smd_stream_read;
}
if (is_word_access_ch(ch->type)) {
ch->read_from_fifo = smd_memcpy32_from_fifo;
ch->write_to_fifo = smd_memcpy32_to_fifo;
} else {
ch->read_from_fifo = smd_memcpy_from_fifo;
ch->write_to_fifo = smd_memcpy_to_fifo;
}
smd_memcpy_from_fifo(ch->name, alloc_elm->name, SMD_MAX_CH_NAME_LEN);
ch->name[SMD_MAX_CH_NAME_LEN-1] = 0;
ch->pdev.name = ch->name;
ch->pdev.id = ch->type;
SMD_INFO("smd_alloc_channel() '%s' cid=%d\n",
ch->name, ch->n);
mutex_lock(&smd_creation_mutex);
list_add(&ch->ch_list, &smd_ch_closed_list);
mutex_unlock(&smd_creation_mutex);
platform_device_register(&ch->pdev);
启动时会为modem创建多个通道,比如
IPCRTR:用于QMI数据收发
DATA40_CNTL:qti进程使用这个通道,对应的设备名字smdcntl8(在设备树中定义)
smdcntl设备
设备树中包含了smdpkt设备,linux启动时执行msm_smd_pkt_probe —> smd_pkt_devicetree_init
smd_pkt_devicetree_init查询设备树中smdpkt的配置,创建字符设备,操作函数为smd_pkt_fops
打开设备
1 | int smd_pkt_open(struct inode *inode, struct file *file) |
写数据
数据直接写入对应的smd通道里
1 | ssize_t smd_pkt_write(struct file *file, |
读取数据
1 |
|
打开时通道时注册了回调ch_notify
1 | static void ch_notify(void *priv, unsigned event) |